﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using Kawanon.Foundation;
using Kawanon.QtHoney;
using Kawanon.Usability;
using QTPlugin;
using QTPlugin.Interop;

namespace Kawanon.Usability.Actions
{
	public class Unknown : Action
	{
		public override string Name { get { return "不明"; } }

		public override void Execute(Arguments args)
		{
			throw new NotImplementedException();
		}
	}

	public class Pending : Action
	{
		public override string Name { get { return "未確定"; } }

		public override void Execute(Arguments args)
		{
			throw new NotImplementedException();
		}
	}

	[Serializable]
	public class MergeWindows : Action
	{
		public override string Name { get { return "ウィンドウをマージ"; } }

		public override void Execute(Arguments args)
		{
			//if (args.Server == null)
			//{
			//    DebugHelpers.Abort("Server == null");
			//}
			object tabBar = Utility.GetField(args.Server, "tabBar");
			if (tabBar == null)
			{
				Dbg.Abort("tabBar == null");
			}

			MethodInfo method = tabBar.GetType().GetMethod("MergeAllWindows",
				BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], null);
			if (method == null)
			{
				Dbg.Abort("method == null");
			}
			object ret = method.Invoke(tabBar, new Object[0]);

			//DebugHelpers.Echo(ret);
		}
	}

	[Serializable]
	public class ShowGestureSettings : Action
	{
		public override string Name { get { return "マウスジェスチャの設定"; } }

		public override void Execute(Arguments args)
		{
			//Utility.DoModal(args.Server, new GestureForm(args.Server));

			OptionsForm form = new OptionsForm(args.Server);
			// マウスジェスチャの設定を選択
			form.SelectedTabIndex = 0;

			Utility.DoModal(args.Server, form);
		}
	}

	[Serializable]
	public class NextTab : Action
	{
		public override string Name { get { return "次のタブへ"; } }

		public static void Go(IPluginServer server)
		{
			ITab[] tabs = server.GetTabs();

			int index = server.SelectedTab.Index;
			index++;
			if (index >= tabs.Length)
			{
				index = 0;
			}
			server.SelectedTab = tabs[index];
		}

		public override void Execute(Arguments args)
		{
			if (args.Server.SelectedTab == null)
			{
				return;
			}

			if (args.Reverse)
			{
				PreviousTab.Go(args.Server);
			}
			else
			{
				Go(args.Server);
			}
		}
	}

	[Serializable]
	public class PreviousTab : Action
	{
		public override string Name { get { return "前のタブへ"; } }

		public static void Go(IPluginServer server)
		{
			ITab[] tabs = server.GetTabs();

			int index = server.SelectedTab.Index;
			index--;
			if (index < 0)
			{
				index = tabs.Length - 1;
			}
			server.SelectedTab = tabs[index];
		}

		public override void Execute(Arguments args)
		{
			if (args.Server.SelectedTab == null)
			{
				return;
			}

			if (args.Reverse)
			{
				NextTab.Go(args.Server);
			}
			else
			{
				Go(args.Server);
			}
		}
	}

	[Serializable]
	public class IsolateTab : Action
	{
		public override string Name { get { return "タブを新しいウィンドウに分離"; } }

		public override void Execute(Arguments args)
		{
			if (args.Server.SelectedTab == null)
			{
				return;
			}

			ITab tab = args.Server.SelectedTab;
			Address addr = tab.Address;
			tab.Close();

			args.Server.CreateWindow(addr);
		}
	}

	[Serializable]
	public class NewTab : Action
	{
		public override string Name { get { return "新しいタブで開く"; } }

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);
			if (addrs == null)
			{
				return;
			}
			/*
			int index = 0;
			if (args.Server.SelectedTab != null)
			{
				index = args.Server.SelectedTab.Index + 1;
			}
			*/
			// 挿入位置は本体の設定にまかせる
			int index = -1;
			Array.ForEach(addrs, addr => args.Server.CreateTab(addr, index, false, true));
		}
	}

	[Serializable]
	public class NewWindow : Action
	{
		public override string Name { get { return "新しいウィンドウで開く"; } }

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);
			if (addrs == null)
			{
				return;
			}

			Array.ForEach(addrs, addr => args.Server.CreateWindow(addr));
		}
	}

	[Serializable]
	class FileOperation : Action
	{
		private const uint SVGIO_BACKGROUND = 0;
		private const uint SVGIO_SELECTION = 0x1;
		private const uint SVGIO_ALLVIEW = 0x2;
		private const uint SVGIO_CHECKED = 0x3;
		private const uint SVGIO_TYPE_MASK = 0xf;
		private const uint SVGIO_FLAG_VIEWORDER = 0x80000000;

		private const uint CMF_NORMAL = 0x00000000;
		private const uint CMF_DEFAULTONLY = 0x00000001;
		private const uint CMF_VERBSONLY = 0x00000002;
		private const uint CMF_EXPLORE = 0x00000004;
		private const uint CMF_NOVERBS = 0x00000008;
		private const uint CMF_CANRENAME = 0x00000010;
		private const uint CMF_NODEFAULT = 0x00000020;
		private const uint CMF_INCLUDESTATIC = 0x00000040;
		private const uint CMF_EXTENDEDVERBS = 0x00000100;

		private const uint GCS_VERBW = 0x00000004;
		private const uint GCS_HELPTEXTW = 0x00000005;
		private const uint GCS_VALIDATEW = 0x00000006;
		private const uint GCS_UNICODE = 0x00000004;

		private const uint S_OK = 0;

		private const uint SW_SHOWNORMAL = 1;

		[DllImport("user32.dll")]
		static extern IntPtr CreatePopupMenu();

		[DllImport("user32.dll")]
		static extern bool DestroyMenu(IntPtr hMenu);

		[DllImport("user32.dll")]
		static extern uint GetMenuItemID(IntPtr hMenu, int nPos);

		[DllImport("user32.dll")]
		static extern int GetMenuItemCount(IntPtr hMenu);

		[StructLayout(LayoutKind.Sequential)]
		protected struct CMINVOKECOMMANDINFO
		{
			public int cbSize;    // sizeof(CMINVOKECOMMANDINFO)
			public int fMask;     // any combination of CMIC_MASK_*
			public IntPtr hwnd;      // might be NULL (indicating no owner window)
			public IntPtr lpVerb;    // either a string or MAKEINTRESOURCE(idOffset)
			public string lpParameters;  // might be NULL (indicating no parameter)
			public string lpDirectory;   // might be NULL (indicating no specific directory)
			public int nShow;     // one of SW_ values for ShowWindow() API
			public int dwHotKey;
			public IntPtr hIcon;
		}

		[ComImport]
		[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
		[Guid("000214f4-0000-0000-c000-000000000046")]
		protected interface IContextMenu2
		{
			[PreserveSig]
			int QueryContextMenu(IntPtr hMenu, uint indexMenu, int idCmdFirst, int idCmdLast, uint uFlags);

			[PreserveSig]
			int InvokeCommand(ref CMINVOKECOMMANDINFO pici);

			[PreserveSig]
			int GetCommandString(int idcmd, uint uflags, int reserved, StringBuilder commandstring, int cch);

			[PreserveSig]
			int HandleMenuMsg(uint uMsg, IntPtr wParam, IntPtr lParam);
		}

		private static Dictionary<string, string> s_names = new Dictionary<string, string>();

		static FileOperation()
		{
			s_names.Add("copy", "ファイルのコピー");
			s_names.Add("cut", "ファイルの切り取り");
			s_names.Add("paste", "ファイルのペースト");
		}

		private string m_verb;

		public override string Name { get { return s_names[m_verb]; } }

		public FileOperation(string verb)
		{
			m_verb = verb;
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_verb.GetHashCode();
		}

		protected bool InvokeCommandByVerb(IntPtr hwnd, IContextMenu2 contextMenu, string verb)
		{
			IntPtr hMenu = CreatePopupMenu();
			int maxId = contextMenu.QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_NOVERBS);

			bool ret = false;
			int count = GetMenuItemCount(hMenu);
			for (int i = 0; i < count; i++)
			{
				int id = (int)GetMenuItemID(hMenu, i);
				if (id == -1 || id == 0)
				{
					continue;
				}

				StringBuilder buf = new StringBuilder(256);
				contextMenu.GetCommandString(id - 1, GCS_VERBW, 0, buf, buf.Capacity);

				if (buf.ToString() == verb)
				{
					CMINVOKECOMMANDINFO ici = new CMINVOKECOMMANDINFO();
					ici.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO));
					ici.hwnd = hwnd;
					ici.lpVerb = new IntPtr(id - 1);
					ici.nShow = (int)SW_SHOWNORMAL;
					ret = (contextMenu.InvokeCommand(ref ici) == S_OK);
					break;
				}
			}

			DestroyMenu(hMenu);
			return ret;
		}

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);
			if (addrs == null)
			{
				return;
			}

			//IShellBrowser browser = (IShellBrowser)Utility.ReflectGetFieldValue(args.Server, "shellBrowser");
			//if (browser == null)
			//{
			//    Dbg.Abort("browser == null");
			//    return;
			//}

			//IShellView view;
			//if (browser.QueryActiveShellView(out view) != S_OK)
			//{
			//    Dbg.Abort("QueryActiveShellView Failed");
			//    return;
			//}

			if (args.ShellView == null)
			{
				Dbg.Abort("ShellView == null");
				return;
			}
			IntPtr hwnd;
			if (args.ShellView.GetWindow(out hwnd) != S_OK)
			{
				Dbg.Abort("GetWindow Failed");
				return;
			}

			Guid IID_IContextMenu = new Guid("000214e4-0000-0000-c000-000000000046");
			object pv;
			if (args.ShellView.GetItemObject(SVGIO_SELECTION, ref IID_IContextMenu, out pv) != S_OK)
			{
				Dbg.Play();
				return;
			}
			IContextMenu2 contextMenu = (IContextMenu2)pv;
			InvokeCommandByVerb(hwnd, contextMenu, m_verb);

		}
	}

	[Serializable]
	public class SendInput : Action
	{
		// 64bit環境でも動作するようメンバを自動で配置させる
		// （FieldOffset に IntPtr.Size を指定できないという糞仕様に対処）
		[StructLayout(LayoutKind.Sequential)]
		struct INPUT
		{
			//[FieldOffset(0)]
			public int type;
			
			//[FieldOffset(4)]
			//public MOUSEINPUT mi;
			
			//[FieldOffset(4)]
			public KEYBDINPUT ki;
			
			//[FieldOffset(4)]
			//public HARDWAREINPUT hi;

			// MOUSEINPUT とのサイズ差を合わせる（たぶん必要ないが）
			public int Margin1;
			public int Margin2;
		}

#pragma warning disable 0649

		struct MOUSEINPUT
		{
			public int dx;
			public int dy;
			public int mouseData;
			public int dwFlags;
			public int time;
			public IntPtr dwExtraInfo;
		}

		struct KEYBDINPUT
		{
			public short wVk;
			public short wScan;
			public int dwFlags;
			public int time;
			public IntPtr dwExtraInfo;
		}

		struct HARDWAREINPUT
		{
			public int uMsg;
			public short wParamL;
			public short wParamH;
		}

#pragma warning restore 0649

		[DllImport("user32.dll")]
		static extern uint MapVirtualKey(uint uCode, uint uMapType);

		[DllImport("user32.dll", EntryPoint="SendInput", SetLastError = true)]
		static extern uint _SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

		[DllImport("user32.dll", SetLastError = false)]
		static extern IntPtr GetMessageExtraInfo();

		[DllImport("user32.dll")]
		static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);

		const int KEYEVENTF_EXTENDEDKEY = 0x1;
		const int KEYEVENTF_KEYUP = 0x2;

		const int INPUT_KEYBOARD = 1;

		struct KeybdInputSummary
		{
			public Keys Key;
			public int Flags;

			public KeybdInputSummary(Keys key, int flags)
			{
				Key = key;
				Flags = flags;
			}
		}

		public enum ShortcutKeys
		{
			None,

			Cut,
			Copy,
			Paste,
			Undo,
			Delete,
			CompletelyDelete,
			SelectAll,
		}

		private static Dictionary<ShortcutKeys, string> s_names = new Dictionary<ShortcutKeys, string>();
		private static Dictionary<ShortcutKeys, KeybdInputSummary[]> s_inputs = new Dictionary<ShortcutKeys, KeybdInputSummary[]>();

		private ShortcutKeys m_shortcut;

		public override string Name { get { return s_names[m_shortcut]; } }

		static SendInput()
		{
			s_names.Add(ShortcutKeys.Cut, "切り取り");
			s_names.Add(ShortcutKeys.Copy, "コピー");
			s_names.Add(ShortcutKeys.Paste, "貼り付け");
			s_names.Add(ShortcutKeys.Undo, "元に戻す");
			s_names.Add(ShortcutKeys.Delete, "削除");
			//NOTE: Delキーの処理でShiftが押されているか見ているっぽいので機能しない
			//s_names.Add(ShortcutKeys.CompletelyDelete, "完全に削除");
			s_names.Add(ShortcutKeys.SelectAll, "全て選択");

			// 左コントロールキーは拡張キーではないはず
			s_inputs.Add(ShortcutKeys.Cut, SummaryOf(Keys.ControlKey, Keys.X));
			s_inputs.Add(ShortcutKeys.Copy, SummaryOf(Keys.ControlKey, Keys.C));
			s_inputs.Add(ShortcutKeys.Paste, SummaryOf(Keys.ControlKey, Keys.V));
			s_inputs.Add(ShortcutKeys.Undo, SummaryOf(Keys.ControlKey, Keys.Z));
			s_inputs.Add(ShortcutKeys.Delete, SummaryOf(Keys.None, Keys.Delete));
			//s_inputs.Add(ShortcutKeys.CompletelyDelete, SummaryOf(Keys.ShiftKey, Keys.Delete));
			s_inputs.Add(ShortcutKeys.SelectAll, SummaryOf(Keys.ControlKey, Keys.A));
		}

		private static KeybdInputSummary[] SummaryOf(Keys modifier, Keys key)
		{
			return new KeybdInputSummary[] {
				new KeybdInputSummary(modifier, 0),
				new KeybdInputSummary(key, 0),
				new KeybdInputSummary(key, KEYEVENTF_KEYUP),
				new KeybdInputSummary(modifier, KEYEVENTF_KEYUP)
			};
		}

		public SendInput(ShortcutKeys shortcut)
		{
			m_shortcut = shortcut;
		}

		public override void Execute(Arguments args)
		{
			if (args.Server != null)
			{
				args.Server.ExecuteCommand(Commands.FocusFileList, null);
			}

			args.KeepSelection = true;

			KeybdInputSummary[] summaries = s_inputs[m_shortcut];

			INPUT[] inputs = Array.ConvertAll(summaries, delegate(KeybdInputSummary summary)
			{
				INPUT input = new INPUT();
				input.type = INPUT_KEYBOARD;
				input.ki.wVk = (short)summary.Key;
				input.ki.wScan = (short)MapVirtualKey((uint)summary.Key, 0);
				input.ki.dwFlags = summary.Flags;
				input.ki.dwExtraInfo = GetMessageExtraInfo();
				input.ki.time = 0;
				return input;
			});
			uint ret = _SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
			if (ret != inputs.Length)
			{
				Dbg.Abort("SendInput eaten "+ ret +" of "+ inputs.Length +" Error: "+ Marshal.GetLastWin32Error());
			}

			//// とりあえず適当
			//keybd_event((byte)Keys.ControlKey, 0x1D, 0, UIntPtr.Zero);
			//keybd_event((byte)Keys.X, 0x2E, 0, UIntPtr.Zero);
			//keybd_event((byte)Keys.X, 0x2E, KEYEVENTF_KEYUP, UIntPtr.Zero);
			//keybd_event((byte)Keys.ControlKey, 0x1D, KEYEVENTF_KEYUP, UIntPtr.Zero);
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_shortcut.GetHashCode();
		}
	}

	[Serializable]
	public class ShowWindow : Action
	{
		const int SW_HIDE = 0;
		const int SW_SHOWNORMAL = 1;
		const int SW_NORMAL = 1;
		const int SW_SHOWMINIMIZED = 2;
		const int SW_SHOWMAXIMIZED = 3;
		public const int SW_MAXIMIZE = 3;
		const int SW_SHOWNOACTIVATE = 4;
		const int SW_SHOW = 5;
		public const int SW_MINIMIZE = 6;
		const int SW_SHOWMINNOACTIVE = 7;
		const int SW_SHOWNA = 8;
		public const int SW_RESTORE = 9;
		const int SW_SHOWDEFAULT = 10;
		const int SW_FORCEMINIMIZE = 11;

		[DllImport("user32.dll", EntryPoint="ShowWindow")]
		static extern bool _ShowWindow(IntPtr hWnd, int nCmdShow);

		private static Dictionary<int, string> s_names = new Dictionary<int, string>();

		static ShowWindow()
		{
			s_names.Add(SW_MAXIMIZE, "最大化");
			s_names.Add(SW_MINIMIZE, "最小化");
			s_names.Add(SW_RESTORE, "元のサイズに戻す");
		}

		private int m_cmdShow;

		public override string Name { get { return s_names[m_cmdShow]; } }

		public ShowWindow(int cmdShow)
		{
			m_cmdShow = cmdShow;
		}

		public override void Execute(Arguments args)
		{
			_ShowWindow(args.Server.ExplorerHandle, m_cmdShow);
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_cmdShow.GetHashCode();
		}
	}

	[Serializable]
	public class CreateProcess : CustomAction
	{
		[Serializable]
		struct Details	// コピー可能なように構造体で
		{
			private const string ExpandStringHelp = "QTTabBar の「アプリケーション」機能と同じキーワードを指定できます。 %f% = 選択したファイル, %d% = 選択したフォルダ, %s% = 選択したファイルとフォルダ, %c% = 現在開いているフォルダ, %cd% = 選択したフォルダまたは現在開いているフォルダ。 パスは括弧つきで連結されます。";
			// 概要:
			//     起動するアプリケーションまたはドキュメントを取得または設定します。
			//
			//TODO: ドキュメントの場合は UseShellExecute = true
			[Category("コマンドライン")]
			[DisplayName("ファイル名")]
			[Description(ExpandStringHelp)]
			[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
			public string FileName { get; set; }

			[Category("コマンドライン")]
			[DisplayName("引数")]
			[Description(ExpandStringHelp)]
			public string Arguments { get; set; }

			[Category("パラメータ")]
			[DisplayName("作業フォルダ")]
			[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
			[Description(ExpandStringHelp)]
			public string WorkingDirectory { get; set; }

			// 概要:
			//     プロセスを起動するときに使用するウィンドウの状態を取得または設定します。
			[Category("パラメータ")]
			[DisplayName("実行時の大きさ")]
			[Description("Normal = 通常表示, Hidden = 非表示, Minimized = 最小化, Maximized = 最大化")]
			[DefaultValue(ProcessWindowStyle.Normal)]
			public ProcessWindowStyle WindowStyle { get; set; }

			// YesNoTypeConverter を使う
			[Category("パラメータ")]
			[DisplayName("コンソールを表示する")]
			[TypeConverter(typeof(YesNoConverter))]
			[DefaultValue(false)]
			public bool ShowConsole { get; set; }

			// 概要:
			//     プロセスを起動できなかったときにエラー ダイアログ ボックスを表示するかどうかを示す値を取得または設定します。
			[Category("パラメータ")]
			[DisplayName("エラーを表示する")]
			[TypeConverter(typeof(YesNoConverter))]
			[DefaultValue(false)]
			public bool ErrorDialog { get; set; }

			//public object[] GetValues()
			//{
			//    return new object[] { FileName, Arguments, WorkingDirectory, WindowStyle, ShowConsole, ErrorDialog };
			//}
		}

		private Details m_details = new Details();

		public override string Name
		{
			get
			{
				return String.Format("{0} の実行", Path.GetFileNameWithoutExtension(m_details.FileName));
			}
		}

		// %1 などを渡せるように
		public override bool IsAcceptTarget { get { return true; } }

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);

			Address currentFolder = new Address();
			if (args.Server != null && args.Server.SelectedTab != null)
			{
				currentFolder = args.Server.SelectedTab.Address;
			}
			PathExpander expander = new PathExpander(currentFolder, addrs);

			ProcessStartInfo info = new ProcessStartInfo();
			info.FileName = expander.Replace(m_details.FileName);
			info.Arguments = expander.Replace(m_details.Arguments);
			info.WorkingDirectory = expander.Replace(m_details.WorkingDirectory);
			info.WindowStyle = m_details.WindowStyle;
			info.CreateNoWindow = !m_details.ShowConsole;
			info.ErrorDialog = m_details.ErrorDialog;

			if (args.Server != null)
			{
				info.ErrorDialogParentHandle = args.Server.ExplorerHandle;
			}

			try
			{
				Process.Start(info);
			}
			catch (Exception)
			{
				//NOTE: ErrorDialog にまかせる？
				//MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		public CreateProcess(string name, string keys) : this()
		{
		}

		public CreateProcess()
		{
		}

		public override DialogResult ShowEditDialog(IntPtr hwnd)
		{
			return ShowPropertiesForm(hwnd, "ファイルの実行 の設定", null, ref m_details,
				details => !String.IsNullOrEmpty(details.FileName));
		}
	}

	[Serializable]
	class OpenApplication : CustomAction
	{

		public override string Name
		{
			get { return "{0} を開く"; }
		}

		public override bool IsAcceptTarget { get { return false; } }

		public override void Execute(Action.Arguments args)
		{
			//NOTE: 本体が未実装のため使えない...
			ProcessStartInfo[] apps = args.Server.GetApplications(null);
			Dbg.Echo(apps);

			foreach (ProcessStartInfo info in apps)
			{
				PropertiesForm form = new PropertiesForm();
				form.PropertyGrid.SelectedObject = info;
				form.ShowDialog();
			}
		}

		public override DialogResult ShowEditDialog(IntPtr hwnd)
		{
			return DialogResult.OK;
		}
	}

	[Serializable]
	public class MinimizeToTray : Action
	{
		public override string Name { get { return "タスクトレイに格納"; } }

		public override void Execute(Arguments args)
		{
			object band = Utility.GetBandObject(args.Server);

			FieldInfo field = band.GetType().GetField("fNowInTray", BindingFlags.NonPublic | BindingFlags.Instance);
			MethodInfo method = band.GetType().GetMethod("ShowTaksbarItem", BindingFlags.NonPublic | BindingFlags.Static);

			field.SetValue(band, true);
			method.Invoke(band, new object[] { args.Server.ExplorerHandle, false });
		}
	}

	[Serializable]
	public class Scroll : Action
	{
		private const uint WM_HSCROLL = 0x0114;
		private const uint WM_VSCROLL = 0x0115;

		private const int SB_HORZ = 0;
		private const int SB_VERT = 1;
		private const int SB_CTL = 2;
		private const int SB_BOTH = 3;

		private const uint SIF_RANGE = 0x1;
		private const uint SIF_PAGE = 0x2;
		private const uint SIF_POS = 0x4;
		private const uint SIF_DISABLENOSCROLL = 0x8;
		private const uint SIF_TRACKPOS = 0x10;
		private const uint SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS;

		private const uint SB_LINEUP = 0;
		private const uint SB_LINELEFT = 0;
		private const uint SB_LINEDOWN = 1;
		private const uint SB_LINERIGHT = 1;
		private const uint SB_PAGEUP = 2;
		private const uint SB_PAGELEFT = 2;
		private const uint SB_PAGEDOWN = 3;
		private const uint SB_PAGERIGHT = 3;
		private const uint SB_THUMBPOSITION = 4;
		private const uint SB_THUMBTRACK = 5;
		private const uint SB_TOP = 6;
		private const uint SB_LEFT = 6;
		private const uint SB_BOTTOM = 7;
		private const uint SB_RIGHT = 7;
		private const uint SB_ENDSCROLL = 8;

		[DllImport("user32.dll")]
		static extern int SetScrollInfo(IntPtr hwnd, int fnBar, [In] ref SCROLLINFO lpsi, bool fRedraw);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, uint wParam, uint lParam);

		[StructLayout(LayoutKind.Sequential)]
		struct SCROLLINFO
		{
			public uint cbSize;
			public uint fMask;
			public int nMin;
			public int nMax;
			public uint nPage;
			public int nPos;
			public int nTrackPos;
		}

		public enum Direction
		{
			Top,
			Right,
			Bottom,
			Left,
		}

		//enum Destination
		//{
		//    Min,
		//    Max,
		//}

		struct ScrollDirectionInfo
		{
			public string Name;
			public uint Msg;
			public uint Direction;

			//public ScrollDirectionInfo(string name, int axis, Destination dest)
			//{
			//    Name = name;
			//    Axis = axis;
			//    Dest = dest;
			//}

			public ScrollDirectionInfo(string name, uint msg, uint direction)
			{
				Name = name;
				Msg = msg;
				Direction = direction;
			}
		}

		private static Dictionary<Direction, ScrollDirectionInfo> s_dirs = new Dictionary<Direction, ScrollDirectionInfo>();

		static Scroll()
		{
			s_dirs.Add(Direction.Top, new ScrollDirectionInfo("上端にスクロール", WM_VSCROLL, SB_TOP));
			s_dirs.Add(Direction.Right, new ScrollDirectionInfo("右端にスクロール", WM_HSCROLL, SB_RIGHT));
			s_dirs.Add(Direction.Bottom, new ScrollDirectionInfo("下端にスクロール", WM_VSCROLL, SB_BOTTOM));
			s_dirs.Add(Direction.Left, new ScrollDirectionInfo("左端にスクロール", WM_HSCROLL, SB_LEFT));
		}

		private Direction m_dir;

		public override string Name { get { return s_dirs[m_dir].Name; } }

		public Scroll(Direction dir)
		{
			m_dir = dir;
		}

		public override void Execute(Arguments args)
		{
			ScrollDirectionInfo info = s_dirs[m_dir];

			//SCROLLINFO sci = new SCROLLINFO();
			//sci.cbSize = (uint)Marshal.SizeOf(typeof(SCROLLINFO));
			//sci.fMask = SIF_ALL;

			//if (!GetScrollInfo(args.ViewHandle, info.Axis, ref sci))
			//{
			//    Dbg.Abort("GetScrollInfo Failed");
			//}

			//sci.fMask = SIF_PAGE;
			//sci.nPage = (uint)((info.Dest == Destination.Max) ? sci.nMax - (sci.nMin + 1) : 0);

			//int pos = SetScrollInfo(args.ViewHandle, info.Axis, ref sci, true);
			//pos.ToString();

			SendMessage(args.ViewHandle, info.Msg, Utility.MakeLong(info.Direction, 0), 0);
			SendMessage(args.ViewHandle, info.Msg, Utility.MakeLong(SB_ENDSCROLL, 0), 0);
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_dir.GetHashCode();
		}
	}

	[Serializable]
	public class ShowGroupMenu : Action
	{
		public override string Name { get { return (m_expandable ? "拡張" : String.Empty) + "グループの表示"; } }

		private bool m_expandable;

		public ShowGroupMenu(bool expandable)
		{
			m_expandable = expandable;
		}

		public override void Execute(Arguments args)
		{
			ContextMenuStrip menu = new ContextMenuStrip();
			//ImageList il = new ImageList();
			//il.ColorDepth = ColorDepth.Depth32Bit;
			//menu.ImageList = il;
			if (args.Server != null)
			{
				menu.Renderer = args.Server.GetMenuRenderer();
			}

			GroupMenuFactory factory = new GroupMenuFactory();
			factory.Server = args.Server;
			factory.Expandable = m_expandable;
			menu.Items.AddRange(factory.CreateItems());

			Point pt = Cursor.Position;
			pt.Offset(-5, -5);

			menu.Show(pt);

			if (menu.Items.Count == 0)
			{
				return;
			}
			ToolStripDropDownItem first = menu.Items[0] as ToolStripDropDownItem;
			if (first == null)
			{
				return;
			}
			first.ShowDropDown();	// カーソル直下に表示した場合、うまく開けない場合があるので
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_expandable.GetHashCode();
		}
	}

	[Serializable]
	public class LockUnlockTab : Action
	{
		public override string Name { get { return "タブをロック"; } }

		public override void Execute(Arguments args)
		{
			ITab tab = args.Server.SelectedTab;
			if (tab == null)
			{
				return;
			}

			tab.Locked = !tab.Locked;
		}
	}

	[Serializable]
	public class OpenFolder : CustomAction
	{
		[Serializable]
		struct Details	// コピー可能なように構造体で
		{
			[Category("設定")]
			[DisplayName("フォルダのパス")]
			[Editor(typeof(FolderPathEditor), typeof(UITypeEditor))]
			public string Path { get; set; }

			[Category("設定")]
			[DisplayName("動作")]
			[DefaultValue(Target.CurrentTab)]
			public Target Target { get; set; }
		}

		[TypeConverter(typeof(EnumDisplayNameConverter))]
		public enum Target
		{
			[EnumDisplayName("現在のタブ")]
			CurrentTab,

			[EnumDisplayName("新しいタブ")]
			NewTab,

			[EnumDisplayName("新しいウィンドウ")]
			NewWindow,
		}

		private Details m_details = new Details();

		public override string Name
		{
			get
			{
				string name = Utility.GetDisplayName(m_details.Path);
				//string name = Path.GetFileName(m_details.Path);
				//if (String.IsNullOrEmpty(name))
				//{
				//    name = m_details.Path;
				//}
				return String.Format("{0} を開く", name);
			}
		}

		public override bool IsAcceptTarget { get { return false; } }

		public override void Execute(Arguments args)
		{
			if (args.Server == null)
			{
				return;
			}

			Address addr = new Address(m_details.Path);
			switch (m_details.Target)
			{
				case Target.CurrentTab:
					args.Server.SelectedTab.Browse(addr);
					break;
				case Target.NewTab:
					args.Server.CreateTab(addr, -1, false, true);
					break;
				case Target.NewWindow:
					args.Server.CreateWindow(addr);
					break;
			}
		}

		public override DialogResult ShowEditDialog(IntPtr hwnd)
		{
			return ShowPropertiesForm(hwnd, "ファイルを開く の設定", null, ref m_details,
				details => !String.IsNullOrEmpty(details.Path));
		}
	}

	[Serializable]
	public class CallPlugin : CustomAction
	{
		enum PluginFunctionKind
		{
			Button,
			//NOTE: これは判別が難しいので保留...
		//	MenuItem,
			ShortcutKey,
			//Option,
		}

		[Serializable]
		class PluginFunction
		{
			public TypeReference TypeReference { get; set; }
			public string Name { get; set; }
			public PluginFunctionKind Kind { get; set; }

			public PluginFunction(TypeReference typeRef, string name, PluginFunctionKind kind)
			{
				TypeReference = typeRef;
				Name = name;
				Kind = kind;
			}

			public override string ToString()
			{
				return Name;
			}
		}

		private PluginFunction m_func;

		public override string Name
		{
			get { return m_func.Name; }
		}

		public override bool IsAcceptTarget { get { return false; } }

		private void PerformClick(ToolStripItem item)
		{
			item.Owner = new ToolStrip();
			item.PerformClick();
		}

		private void Call(IPluginClient client, IPluginServer server)
		{
			try
			{
				client.Open(server, Utility.GetShellBrowser(server));
			}
			catch (Exception ex)
			{
				// MainPlugin 対策
				string name = ex.GetType().Name;
				if (!name.EndsWith("AlreadyAttachedException"))
				{
					//MessageBox.Show(ex.Message, name, MessageBoxButtons.OK, MessageBoxIcon.Error);
					throw ex;
				}
			}

			IBarButton barButton = client as IBarButton;
			if (barButton != null)
			{
				barButton.InitializeItem();
				barButton.OnButtonClick();
			}

			IBarCustomItem customItem = client as IBarCustomItem;
			if (customItem != null)
			{
				ToolStripItem item = customItem.CreateItem(false, DisplayStyle.NoLabel);
				PerformClick(item);
			}

			IBarMultipleCustomItems customItems = client as IBarMultipleCustomItems;
			if (customItems != null)
			{
				customItems.Initialize(null);

				for (int i = 0; i < customItems.Count; i++)
				{
					if (customItems.GetName(i) == Name)
					{
						ToolStripItem item = customItems.CreateItem(false, DisplayStyle.NoLabel, i);
						PerformClick(item);
					}
				}
			}

			client.Close(EndCode.Unloaded);
		}

		public override void Execute(Action.Arguments args)
		{
			if (args.Server == null)
			{
				return;
			}

			IPluginClient client;
			try
			{
				object obj = m_func.TypeReference.CreateInstance();
				client = obj as IPluginClient;
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message, ex.GetType().Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			if (client == null)
			{
				return;
			}

			Call(client, args.Server);
		}

		private PluginAttribute GetPluginAttribute(Type t)
		{
			object[] a = t.GetCustomAttributes(typeof(PluginAttribute), false);
			if (a.Length > 0)
			{
				return (PluginAttribute)a[0];
			}
			else
			{
				return null;
			}
		}

		private IEnumerable<PluginFunction> EnumeratePluginFunction(Type t)
		{
			object obj = Activator.CreateInstance(t);
			IBarButton barButton = obj as IBarButton;
			IBarCustomItem customItem = obj as IBarCustomItem;
			if (barButton != null || customItem != null)
			{
				PluginAttribute attr = GetPluginAttribute(t);
				// barButton.Text では NullReference となる
				yield return new PluginFunction(
					TypeReference.From(t),
					attr != null ? attr.Name : t.Name, PluginFunctionKind.Button);
			}
			IBarMultipleCustomItems customItems = obj as IBarMultipleCustomItems;
			if (customItems != null)
			{
				customItems.Initialize(null);
				int count = customItems.Count;
				if (count == -1)
				{
					yield break;
				}
				for (int i = 0; i < count; i++)
				{
					yield return new PluginFunction(
						TypeReference.From(t), customItems.GetName(i), PluginFunctionKind.Button);
				}
			}
		}

		private PluginFunction[] GetPluginFunctions(string path)
		{
			string ifaceName = typeof(IPluginClient).FullName;
			Assembly asm = Assembly.LoadFile(path);
			Type[] types = Array.FindAll(asm.GetTypes(), delegate(Type t)
			{
				return t.IsClass && t.IsPublic && !t.IsAbstract &&
					t.GetInterface(ifaceName) != null;
			});

			List<PluginFunction> list = new List<PluginFunction>();
			foreach (Type t in types)
			{
				list.AddRange(EnumeratePluginFunction(t));
			}
			return list.ToArray();
		}

		public override DialogResult ShowEditDialog(IntPtr hwnd)
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Filter = "QTTabBar プラグイン (*.dll)|*.dll";
			if (m_func != null)
			{
				dlg.FileName = m_func.TypeReference.Path;
			}

			DialogResult res = dlg.ShowDialog(Form.FromHandle(hwnd));
			if (res == DialogResult.Cancel)
			{
				return DialogResult.Cancel;
			}

			PluginFunction[] funcs = GetPluginFunctions(dlg.FileName);
			if (funcs.Length == 0)
			{
				MessageBox.Show("このプラグインは呼び出せません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return DialogResult.Cancel;
			}
			PluginFunction func = Utility.ChooseFromArray(Form.FromHandle(hwnd), funcs);
			if (func == null)
			{
				return DialogResult.Cancel;
			}

			//func.TypeReference.ForceLoad = true;
			m_func = func;
			return DialogResult.OK;
		}
	}

	[Serializable]
	public class CopyPath : Action
	{
		private const uint CLIPBRD_E_CANT_OPEN = 0x800401D0;

		public enum Source
		{
			Selection,
			SelectedTab,
			Tabs,
		}

		private Source m_src;

		public CopyPath(Source src)
		{
			m_src = src;
		}

		public override string Name
		{
			get
			{
				switch (m_src)
				{
					case Source.Selection:
						return "選択ファイルのパスをコピー";
					case Source.SelectedTab:
						return "タブのパスをコピー";
					case Source.Tabs:
						return "全てのタブのパスをコピー";
				}
				throw new Exception();
			}
		}

		public override void Execute(Arguments args)
		{
			string[] paths = null;
			switch (m_src)
			{
				case Source.Selection:
					paths = GetFileNames(args.Target);
					break;
				case Source.SelectedTab:
					paths = new string[] { Utility.AddressToPath(args.Server.SelectedTab.Address) };
					break;
				case Source.Tabs:
					paths = Array.ConvertAll(args.Server.GetTabs(), delegate(ITab tab)
					{
						return Utility.AddressToPath(tab.Address);
					});
					break;
			}

			if (paths == null)
			{
				return;
			}

			try
			{
				Utility.UseWaitCursor(() => Clipboard.SetText(String.Join("\r\n", paths)));
			}
			catch (ExternalException ex)
			{
				//SEE: http://d.hatena.ne.jp/ladybug/20050903
				if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN)
				{
					throw ex;
				}
			}
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_src.GetHashCode();
		}
	}

	[Serializable]
	public class CloseAllUnlocked : Action
	{
		public override string Name { get { return "ロックタブ以外を閉じる"; } }

		public override void Execute(Arguments args)
		{
			object tabBar = Utility.GetBandObject(args.Server);
			if (tabBar == null)
			{
				Dbg.Abort("tabBar == null");
			}

			MethodInfo method = tabBar.GetType().GetMethod("CloseAllUnlocked",
				BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null)
			{
				Dbg.Abort("method == null");
			}
			method.Invoke(tabBar, new Object[0]);
		}
	}

	[Serializable]
	public class ShowQtHoneySettings : Action
	{
		public override string Name { get { return "QTHoneyの設定"; } }

		public override void Execute(Arguments args)
		{
			Utility.DoModal(args.Server, new OptionsForm(args.Server));
		}
	}

	[Serializable]
	public class GoFirstOrLast : Action
	{
		private bool m_back = false;

		public override string Name { get { return m_back ? "最後まで戻る" : "先頭まで進む"; } }

		public override void Execute(Arguments args)
		{
			object tabBar = Utility.GetBandObject(args.Server);
			if (tabBar == null)
			{
				Dbg.Abort("tabBar == null");
			}

			MethodInfo method = tabBar.GetType().GetMethod("NavigateToFirstOrLast",
				BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(bool) }, null);
			if (method == null)
			{
				Dbg.Abort("method == null");
			}
			method.Invoke(tabBar, new object[] { m_back });
		}

		public GoFirstOrLast(bool back)
		{
			m_back = back;
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_back.GetHashCode();
		}
	}

	[Serializable]
	public class LastActiveTab : Action
	{
		public override string Name { get { return "直前のタブに移動"; } }

		public override void Execute(Arguments args)
		{
			object band = Utility.GetBandObject(args.Server);

			ToolStripItem item = (ToolStripItem)Utility.GetField(band, "tsmiLastActiv");
			item.PerformClick();
		}
	}

	[Serializable]
	public class CloneTab : Action
	{

		public override string Name { get { return "タブをクローン"; } }

		public override void Execute(Action.Arguments args)
		{
			if (args.Server == null || args.Server.SelectedTab == null)
			{
				return;
			}

			args.Server.SelectedTab.Clone(-1, true);
		}
	}

}
